package island.gotenyama.wand.bean;

import island.gotenyama.wand.annotation.FieldDefinition;
import island.gotenyama.wand.annotation.TableDefinition;
import island.gotenyama.wand.exception.FatalException;
import island.gotenyama.wand.field.ACmnField;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

/**
 * クエリを表すクラス
 * 
 * @author tk
 */
public class Query<T extends AField> {

	/**
	 * クエリ実行役(SpringJDBC)
	 */
	protected SimpleJdbcTemplate simpleJdbcTemplate;

	/**
	 * テーブル定義
	 */
	public final TableDefinition defTable;

	/**
	 * Javaのフィールド
	 */
	public final List<Field> fieldList;

	/**
	 * このアプリケーション特有のフィールド
	 */
	public final List<AField> memberList;

	/**
	 * 標準検索文
	 */
	public final String defSelectSQL;

	/**
	 * 標準登録文
	 */
	public final String defInsertSQL;

	/**
	 * 標準更新文
	 */
	public final String defUpdateSQL;

	/**
	 * 標準削除文
	 */
	public final String defDeleteSQL;

	/**
	 * 標準行マッパー
	 */
	public final RowMapper<DataRow> defMapper;

	/**
	 * コンストラクタ
	 * 
	 * @param <T> 任意のフィールド型
	 * @param dataSource データソース
	 * @param baseDef 任意のフィールド型
	 */
	public Query(DataSource dataSource, Class<T> baseDef) {
		this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
		if (baseDef == null) {
			throw new FatalException("元となる定義がない");
		}
		Annotation[] aArray = baseDef.getAnnotations();
		if (aArray == null || aArray.length < 1) {
			throw new FatalException("アノテーションが宣言されていない");
		}
		TableDefinition tableDef = null;
		for (Annotation ann : aArray) {
			if (ann instanceof TableDefinition) {
				tableDef = (TableDefinition) ann;
				break;
			}
		}
		if (tableDef == null) {
			throw new FatalException("アノテーションが宣言されていない");
		}
		defTable = tableDef;

		// Field
		final List<Field> fieldList = new ArrayList<Field>();
		Field[] aCmnFieldArray = ACmnField.class.getFields();
		if (tableDef.hasACmnField()) {
			fieldList.add(aCmnFieldArray[0]);
		}
		for (Field field : baseDef.getFields()) {
			if (field.isEnumConstant()) {
				fieldList.add(field);
			}
		}
		if (tableDef.hasACmnField()) {
			fieldList.add(aCmnFieldArray[1]);
			fieldList.add(aCmnFieldArray[2]);
			fieldList.add(aCmnFieldArray[3]);
			fieldList.add(aCmnFieldArray[4]);
		}
		this.fieldList = Collections.unmodifiableList(fieldList);

		// AField
		final List<AField> aFieldList = new ArrayList<AField>();
		try {
			for (Field field : fieldList) {
				if (field.isEnumConstant()) {
					Object obj = field.get(null);
					if (obj instanceof AField) {
						aFieldList.add((AField) field.get(null));
					}
				}
			}
		} catch (IllegalAccessException e) {
			final String errMsg = "フィールドアクセスに失敗。";
			throw new FatalException(errMsg, e);
		}
		this.memberList = Collections.unmodifiableList(aFieldList);

		// SQL
		String tname = tableDef.name();
		StringBuilder selectStr = new StringBuilder("select ");
		StringBuilder insertStr = new StringBuilder("insert into " + tname + " (");
		StringBuilder updateStr = new StringBuilder("update " + tname + "set ");
		StringBuilder deleteStr = new StringBuilder("delete from " + tname);
		for (int i = 0; i < fieldList.size(); i++) {
			Field field = fieldList.get(i);
			AField aField = aFieldList.get(i);
			FieldDefinition fieldDef = field.getAnnotation(FieldDefinition.class);
			if (fieldDef != null) {
				String fname = fieldDef.name();
				if (aField == ACmnField.ID) {
					selectStr.append(fname + ",");
				} else if (aField == ACmnField.CREATE_USER) {
					selectStr.append(fname + ",");
					insertStr.append(fname + ",");
				} else if (aField == ACmnField.CREATE_DATE) {
					selectStr.append(fname + ",");
					insertStr.append(fname + ",");
				} else if (aField == ACmnField.UPDATE_USER) {
					selectStr.append(fname + ",");
					insertStr.append(fname + ",");
					updateStr.append(fname + " = :" + fname + ",");
				} else if (aField == ACmnField.UPDATE_DATE) {
					selectStr.append(fname + " from " + tname + " ");
					insertStr.append(fname + ") values (");
					updateStr.append(fname + " = now()");
				} else if (i != (fieldList.size() - 1)) {
					selectStr.append(fname + ",");
					insertStr.append(fname + ",");
					updateStr.append(fname + " = :" + fname + ",");
				} else {
					selectStr.append(fname + " from " + tname + " ");
					insertStr.append(fname + ") values (");
					updateStr.append(fname + " = :" + fname);
				}
			}
		}
		for (int i = 0; i < fieldList.size(); i++) {
			Field field = fieldList.get(i);
			AField aField = aFieldList.get(i);
			FieldDefinition fieldDef = field.getAnnotation(FieldDefinition.class);
			if (fieldDef != null) {
				String fname = fieldDef.name();
				if (aField == ACmnField.ID) {
				} else if (aField == ACmnField.CREATE_DATE) {
					insertStr.append("now(),");
				} else if (aField == ACmnField.UPDATE_DATE) {
					insertStr.append("now())");
				} else if (i != (fieldList.size() - 1)) {
					insertStr.append(":" + fname + ",");
				} else {
					insertStr.append(":" + fname + ")");
				}
			}
		}
		defSelectSQL = selectStr.toString();
		defInsertSQL = insertStr.toString();
		defUpdateSQL = updateStr.toString();
		defDeleteSQL = deleteStr.toString();
		this.defMapper = new RowMapper<DataRow>() {
			@Override
			public DataRow mapRow(ResultSet rs, int rowNum) throws SQLException {
				DataRow resultRow = new DataRow();
				for (AField field : aFieldList) {
					resultRow.put(field, rs);
				}
				return resultRow;
			}
		};
	}

	/**
	 * 検索する。
	 * 
	 * @param sql　SQL文
	 * @param rm 行マッパー
	 * @param key 主キー
	 * @return 検索結果
	 */
	public DataRow select(RowMapper<DataRow> rm, Long key) {
		if (!defTable.hasACmnField()) {
			throw new FatalException("共通フィールドではない");
		}
		String sql = defSelectSQL + " where id = :id";
		return this.simpleJdbcTemplate.queryForObject(sql, rm, key);
	}

	/**
	 * 検索する。
	 * 
	 * @param sql　SQL文
	 * @param rm 行マッパー
	 * @param params　パラメータ
	 * @return 検索結果
	 */
	public DataRow select(String sql, RowMapper<DataRow> rm, QueryParams params) {
		return this.simpleJdbcTemplate.queryForObject(sql, rm, params);
	}

	/**
	 * 検索(複数期待)する。
	 * 
	 * @param sql　SQL文
	 * @param rm 行マッパー
	 * @param params　パラメータ
	 * @return 検索結果
	 */
	public List<DataRow> selectList(String sql, RowMapper<DataRow> rm, QueryParams params) {
		return this.simpleJdbcTemplate.query(sql, rm, params);
	}

	/**
	 * 検索(複数期待)する。
	 * 
	 * @param sql　SQL文
	 * @param rm
	 * @return 検索結果
	 */
	public List<DataRow> selectAll(String sql, RowMapper<DataRow> rm) {
		return this.simpleJdbcTemplate.query(sql, rm);
	}

	/**
	 * 件数をintで取得する。
	 * 
	 * @param sql　SQL文
	 * @param params　パラメータ
	 * @return　件数
	 */
	public int getIntCount(String sql, QueryParams params) {
		return this.simpleJdbcTemplate.queryForInt(sql, params);
	}

	/**
	 * 件数をlongで取得する。
	 * 
	 * @param sql　SQL文
	 * @param params　パラメータ
	 * @return　件数
	 */
	public long getLongCount(String sql, QueryParams params) {
		return this.simpleJdbcTemplate.queryForLong(sql, params);
	}

	/**
	 * 標準のSQL文でデータ登録する。
	 * 
	 * @param params　パラメータ
	 * @return　主キー
	 */
	public long insert(QueryParams params) {
		if (defTable.hasACmnField()) {
			params.add(ACmnField.CREATE_USER, "testInsert");
			params.add(ACmnField.UPDATE_USER, "testInsert");
		}
		int cnt = this.simpleJdbcTemplate.update(defInsertSQL, params);
		if (cnt > 0) {
			return this.simpleJdbcTemplate.queryForLong("select LAST_INSERT_ID()");
		} else {
			final String errMsg = "データ登録失敗。結果件数が0件。";
			throw new FatalException(errMsg);
		}
	}

	/**
	 * データ登録する。
	 * 
	 * @param sql　SQL文
	 * @param params　パラメータ
	 * @return　主キー
	 */
	public long insert(String sql, QueryParams params) {
		if (defTable.hasACmnField()) {
			params.add(ACmnField.CREATE_USER, "testInsert");
			params.add(ACmnField.UPDATE_USER, "testInsert");
		}
		int cnt = this.simpleJdbcTemplate.update(sql, params);
		if (cnt > 0) {
			return this.simpleJdbcTemplate.queryForLong("select LAST_INSERT_ID()");
		} else {
			final String errMsg = "データ登録失敗。結果件数が0件。";
			throw new FatalException(errMsg);
		}
	}

	/**
	 * 標準のSQL文でデータ更新する。
	 * 
	 * @param params　パラメータ
	 * @return　更新件数
	 */
	public int update(QueryParams params) {
		if (defTable.hasACmnField()) {
			params.add(ACmnField.UPDATE_USER, "testUpdate");
		}
		return this.simpleJdbcTemplate.update(defUpdateSQL, params);

	}

	/**
	 * データ更新する。
	 * 
	 * @param sql　SQL文
	 * @param params　パラメータ
	 * @return　更新件数
	 */
	public int update(String sql, QueryParams params) {
		if (defTable.hasACmnField()) {
			params.add(ACmnField.UPDATE_USER, "testUpdate");
		}
		return this.simpleJdbcTemplate.update(sql, params);
	}

	/**
	 * 標準のSQL文でデータ削除する。
	 * 
	 * @param sql　SQL文
	 * @param params　パラメータ
	 * @return　削除件数
	 */
	public int delete(String sql, QueryParams params) {
		return this.simpleJdbcTemplate.update(sql, params);
	}
}
